package cadtoolbox.model;

import java.io.Serializable;

public class Template <E> implements Serializable {	

  private static final long serialVersionUID = 4394186475468043658L;
  public double totalConcentration;
  public double concentrationAlone;
  public double concentrationWithInput;
  public double concentrationWithOutput;
  public double concentrationWithBoth;
  public double concentrationExtended;
  public double concentrationInhibited;
  private double inputSlowdown = 1;
  private double outputSlowdown = 1;
  private double stackSlowdown = 1;
  private OligoGraph<SequenceVertex, E> parent;
  private SequenceVertex from;
  private SequenceVertex to;
  private SequenceVertex inhib;
  double poly = Constants.polVm / Constants.polKm;
  private double polyboth = Constants.polVm / Constants.polKmBoth;
  double nick = Constants.nickVm / Constants.nickKm;

  public void reset(double totalConcentration, SequenceVertex from, SequenceVertex to, SequenceVertex inhib)
  {
    this.totalConcentration = totalConcentration;
    this.concentrationAlone = totalConcentration;
    this.concentrationExtended = 0.0D;
    this.concentrationInhibited = 0.0D;
    this.concentrationWithBoth = 0.0D;
    this.concentrationWithOutput = 0.0D;
    this.concentrationWithInput = 0.0D;
    this.from = from;
    this.to = to;
    this.inhib = inhib;
  }

  public Template(OligoGraph<SequenceVertex, E> parent, double totalConcentration, SequenceVertex from, SequenceVertex to, SequenceVertex inhib)
  {
    this.parent = parent;
    this.totalConcentration = totalConcentration;
    this.concentrationAlone = totalConcentration;
    this.concentrationExtended = 0.0D;
    this.concentrationInhibited = 0.0D;
    this.concentrationWithBoth = 0.0D;
    this.concentrationWithOutput = 0.0D;
    this.concentrationWithInput = 0.0D;
    this.from = from;
    this.to = to;
    this.inhib = inhib;
    
    if(!from.sequence.equals("") && !to.sequence.equals("")){
    	inputSlowdown = SequenceDependent.getInputSlowdown(from.sequence, to.sequence);
    	outputSlowdown = SequenceDependent.getOutputSlowdown(from.sequence, to.sequence);
    	stackSlowdown = SequenceDependent.getStackSlowdown(from.sequence, to.sequence);
    }
  }

  public double[] flux() {
    double[] ans = { aloneFlux(), inputFlux(), outputFlux(), bothFlux(), 
      extendedFlux(), inhibitedFlux() };
    return ans;
  }

  public double inhibSequenceFlux() {
    double conc = this.inhib.getConcentration();
    return ((Double)this.parent.K.get(this.inhib)).doubleValue() * Constants.alpha * Constants.Kduplex * 
      this.concentrationInhibited - 
      Constants.Kduplex * 
      conc * (
      this.concentrationAlone + this.concentrationWithInput + 
      this.concentrationWithOutput)
      + Constants.Kduplex*this.concentrationInhibited*(Constants.ratioToeholdLeft*this.from.getConcentration()+Constants.ratioToeholdRight*this.to.getConcentration());
  }

  public double inputSequenceFlux() {
    double conc = this.from.getConcentration();
    double kinhib;
    double inhib;
    if (this.inhib != null) {
      inhib = this.inhib.getConcentration();
      kinhib = Constants.Kduplex;
    }
    else {
      inhib = 0.0D;
      kinhib = 0.0D;
    }
    double debug = ((Double)this.parent.K.get(this.from)).doubleValue() *Constants.Kduplex * (
      inputSlowdown*this.concentrationWithInput + stackSlowdown*this.concentrationWithBoth) + 
      kinhib * 
      inhib * 
      this.concentrationWithInput - 
      Constants.Kduplex * 
      conc * (
      this.concentrationAlone + this.concentrationWithOutput + Constants.ratioToeholdLeft*this.concentrationInhibited);

    return debug;
  }

  public double outputSequenceFlux() {
    double conc = this.to.getConcentration();
    double kinhib;
    double inhib;
    if (this.inhib != null) {
      inhib = this.inhib.getConcentration();
      kinhib = Constants.Kduplex;
    }
    else {
      inhib = 0.0D;
      kinhib = 0.0D;
    }
    double pol;
    if (this.parent.isInhibitor(this.to))
      pol = this.polyboth * Constants.displ;
    else {
      pol = this.poly;
    }
    
    double debug = ((Double)this.parent.K.get(this.to)).doubleValue() * Constants.Kduplex * (
      outputSlowdown*this.concentrationWithOutput + stackSlowdown*this.concentrationWithBoth) + 
      kinhib * 
      inhib * 
      this.concentrationWithOutput - 
      Constants.Kduplex * 
      conc * (
      this.concentrationAlone + this.concentrationWithInput + Constants.ratioToeholdRight*this.concentrationInhibited) + 
      pol * 
      this.concentrationWithBoth;

    return debug;
  }

  private double aloneFlux() {
    double concIn = this.from.getConcentration();
    double concOut = this.to.getConcentration();
    double kreleaseInhib;
    double concInhib;
    double selfstart = 0;
    if(parent.selfStart){
    	selfstart = Constants.ratioSelfStart;
    }
    if (this.inhib != null) {
      concInhib = this.inhib.getConcentration();
      kreleaseInhib = ((Double)this.parent.K.get(this.inhib)).doubleValue() * Constants.alpha * Constants.Kduplex;
    } else {
      concInhib = 0.0D;
      kreleaseInhib = 0.0D;
    }
    
    if(concIn>1){
    	//System.out.println("");
    }

    double debug = ((Double)this.parent.K.get(this.from)).doubleValue() * inputSlowdown*Constants.Kduplex * 
      this.concentrationWithInput + 
      ((Double)this.parent.K.get(this.to)).doubleValue() * outputSlowdown*Constants.Kduplex * 
      this.concentrationWithOutput + 
      kreleaseInhib * 
      this.concentrationInhibited - 
      this.concentrationAlone * 
      Constants.Kduplex * (
      concIn + concOut + concInhib)
      -this.concentrationAlone*selfstart*poly;

    return debug;
  }

  private double inputFlux() {
    double concIn = this.from.getConcentration();
    double concOut = this.to.getConcentration();
    double concInhib;

    if (this.inhib != null)
      concInhib = this.inhib.getConcentration();
    else {
      concInhib = 0.0D;
    }
    return Constants.Kduplex * 
      concIn * 
      (this.concentrationAlone + Constants.ratioToeholdLeft*this.concentrationInhibited) + 
      ((Double)this.parent.K.get(this.to)).doubleValue() * stackSlowdown* Constants.Kduplex * 
      this.concentrationWithBoth - 
      this.concentrationWithInput * (
      ((Double)this.parent.K.get(this.from)).doubleValue() * inputSlowdown* Constants.Kduplex + this.poly + Constants.Kduplex * 
      concOut + Constants.Kduplex * 
      concInhib);
  }

  private double outputFlux()
  {
    double concIn = this.from.getConcentration();
    double concOut = this.to.getConcentration();
    double concInhib;
    double selfstart = 0;
    if(parent.selfStart){
    	selfstart = Constants.ratioSelfStart;
    }
    if (this.inhib != null)
      concInhib = this.inhib.getConcentration();
    else {
      concInhib = 0.0D;
    }
    double debug = concOut * 
      Constants.Kduplex * 
      (this.concentrationAlone + Constants.ratioToeholdRight*this.concentrationInhibited) + 
      ((Double)this.parent.K.get(this.from)).doubleValue() * stackSlowdown*Constants.Kduplex * 
      this.concentrationWithBoth - 
      this.concentrationWithOutput * (
      ((Double)this.parent.K.get(this.to)).doubleValue() * outputSlowdown*Constants.Kduplex + Constants.Kduplex * concIn + Constants.Kduplex * 
      concInhib)
      + this.concentrationAlone*selfstart*poly;

    return debug;
  }

  private double bothFlux() {
    double concIn = this.from.getConcentration();
    double concOut = this.to.getConcentration();
    double pol = this.parent.isInhibitor(this.to) ? this.polyboth * Constants.displ : 
      this.poly;
    
    return Constants.Kduplex * concIn * this.concentrationWithOutput + 
      Constants.Kduplex * concOut * this.concentrationWithInput + 
      this.nick * this.concentrationExtended - 
      this.concentrationWithBoth * stackSlowdown*(((Double)this.parent.K.get(this.from)).doubleValue() * Constants.Kduplex + ((Double)this.parent.K.get(this.to)).doubleValue() * Constants.Kduplex + pol);
  }

  private double extendedFlux() {
    double poldispl = this.parent.isInhibitor(this.to) ? this.polyboth * Constants.displ : 
      this.poly;

   
    return this.poly * 
      this.concentrationWithInput + 
      poldispl * 
      this.concentrationWithBoth - 
      this.nick * this.concentrationExtended;
  }

  private double inhibitedFlux()
  {
    double krelease;
    double conc;

    if (this.inhib != null) {
      conc = this.inhib.getConcentration();
      krelease = ((Double)this.parent.K.get(this.inhib)).doubleValue() * Constants.alpha * Constants.Kduplex;
    } else {
      conc = 0.0D;
      krelease = 0.0D;
    }

    return -krelease * 
      this.concentrationInhibited 
      - this.concentrationInhibited*Constants.Kduplex*(Constants.ratioToeholdLeft*this.from.getConcentration()+Constants.ratioToeholdRight*this.to.getConcentration())
       + Constants.Kduplex * 
      conc * (
      this.concentrationAlone + this.concentrationWithInput + 
      this.concentrationWithOutput);
  }

  public double getPolyUsage()
  {
    return this.concentrationWithInput / Constants.polKm + this.concentrationWithBoth / Constants.polKmBoth;
  }

  public void setAllocatedPoly(double observedKm) {
    this.poly = (Constants.polVm)/(Constants.polKm*observedKm);
    this.polyboth = Constants.polVm/(Constants.polKmBoth*observedKm);
  }

  public double getNickUsage() {
    return this.concentrationExtended / Constants.nickKm;
  }

  public void setAllocatedNick(double observedKm) {
    this.nick = Constants.nickVm/(Constants.nickKm*observedKm);
  }

  public double[] getStates() {
    double[] ans = { this.concentrationAlone, this.concentrationWithInput, 
      this.concentrationWithOutput, this.concentrationWithBoth, 
      this.concentrationExtended, this.concentrationInhibited };
    return ans;
  }

  public void setStates(double[] values) throws InvalidConcentrationException {
    if (values.length != 6) {
      System.err
        .println("Error: wrong internal values setting for template " + 
        this);
      return;
    }
    for (int i = 0; i < 6; i++) {
      if ((values[i] < 0.0D) || (values[i] > this.totalConcentration+1))
      {
        values[i] = 0.0D;
      }
    }

    this.concentrationAlone = values[0];
    this.concentrationWithInput = values[1];
    this.concentrationWithOutput = values[2];
    this.concentrationWithBoth = values[3];
    this.concentrationExtended = values[4];
    this.concentrationInhibited = values[5];
  }

  public SequenceVertex getFrom() {
    return this.from;
  }

  public SequenceVertex getTo() {
    return this.to;
  }

  public double totalConcentration() {
    return this.concentrationAlone + this.concentrationExtended + 
      this.concentrationInhibited + this.concentrationWithBoth + 
      this.concentrationWithInput + this.concentrationWithOutput;
  }

  public void reset() {
    this.concentrationAlone = this.totalConcentration;
    this.concentrationExtended = 0.0D;
    this.concentrationInhibited = 0.0D;
    this.concentrationWithBoth = 0.0D;
    this.concentrationWithOutput = 0.0D;
    this.concentrationWithInput = 0.0D;
    this.poly = (Constants.polVm / Constants.polKm);
    this.nick = (Constants.nickVm / Constants.nickKm);
  }

  public String toString()
  {
    return "S" + this.from.ID + "->" + (this.parent.isInhibitor(this.to) ? "I(" + this.parent.inhibitors.get(this.to).toString() + ")" : new StringBuilder("S").append(this.to.ID).toString());
  }
}